public class Gesture {
  // elimino i valori "correnti" dagli attributi, salvo solo i dati di cui devo tenere memoria
  float[] prevCoords;
  float prevLeftY, prevDerLeft, prevRightY, prevDerRight;
  long prevMills;
  int minBeat, beatDirection, prova;

  public Gesture(int minBeat) {
    prevCoords = new float[4];
    beatDirection = 1;
    setMinBeat(minBeat);
  }

  public Gesture() {
    // un mezzo beat (battere o levare) ogni 100ms = un beat ogni 200ms = 300bpm come massimo metronomo
    this(140);
  }

  public void setMinBeat(int minBeat) {
    this.minBeat = minBeat;
  }

  // avevi ragione tu... beat e volume sono strettamente legati, meglio tornare al metodo singolo
  public float process(float leftX, float leftY, float rightX, float rightY, int minMov, float movRight) {
    float result = 0;
    float firstDerLeft = leftY - prevLeftY;
    float firstDerRight = rightY - prevRightY;
    long mills = millis();
    // la funzione beatRule va a cercare un battere se l'ultimo era un beat in levare e viceversa
    // avrei potuto salvare anche firstDer e mills come attributi e scrivere un metodo senza argomenti
    // non lo faccio perché così dico esplicitamente che prima di calcolare il beat devo anche
    // calcolare derivata prima e tenere traccia del tempo corrente
    if (beatRule(firstDerLeft,  mills)) {
      // passo da battere a levare e viceversa
      beatDirection = -beatDirection;
      float vol = 0;
      float[] currCoords = new float[4];
      if (beatDirection > 0) {
        prova = 1;
      }
      else prova = -1;
      // il metodo ritorna 0 se non trova beat, l'intensità con segno + per il battere
      // e l'intensità con segno - per il levare
      
      vol = abs(rightY - h) / (h - h/4);
      result = prova * vol;
      // salvo lo stato per l'iterazione successiva negli attributi della classe
      prevMills = mills;
      
    }
    prevLeftY = leftY;
    prevDerLeft = firstDerLeft;
    prevRightY = rightY;
    prevDerRight = firstDerRight;
    
    return result;
  }

  // leggo il valore di beatDirection per capire se sono in battere o levare
  // sfrutto l'informazione per cambiare la condizione di controllo del prossimo beat
  private boolean beatRule(float firstDerLeft, long mills) {
    if (beatDirection > 0) {
      return firstDerLeft >= 0 && prevDerLeft < 0 && mills - prevMills >= minBeat;
    }
    return firstDerLeft <= 0 && prevDerLeft > 0 && mills - prevMills >= minBeat;
  }
}

